#ifndef DEVICE_WRAPPER_HPP

#include "Types.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <ostream>
#include <fstream>

class DeviceWrapper {
public:	
	std::vector<cl::Device> devices;
	DeviceWrapper(cl::Platform &platform)
	{
		platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);
	}
	
	DeviceWrapper(cl::Device &device)
	{
		devices.push_back(device);
	}
	
	void getDeviceProgram(const std::string &filein, cl::Device &deviceSel,cl::Context & context,cl::CommandQueue &cmdq,cl::Program::Sources &sources,cl::Program &program  )
	{
			if (devices.size() ==1)
			deviceSel= devices[0];
		else
		{
			std::cout << "\n\nPlease enter the Device Number to be selected (0 to  " << (devices.size()-1)<< " ):" <<std::endl;
			unsigned i;
			std::cin >> i;
			if (i >(devices.size()-1))
			{
				std::cerr <<" Incorrect Number entered. Exiting" ;
				system("pause");
				exit(EXIT_FAILURE);				
			}
			else
				deviceSel= devices[i];	
		}

		std::vector<cl::Device> device_vec;
		device_vec.push_back(deviceSel);

		// Creeating the Context with the Selected Devices
		context= cl::Context(device_vec);

		// Creeating the CommandQueue with the available context and the Selected device
		cmdq=cl::CommandQueue(context, deviceSel);
		std::ifstream fin(filein.c_str());
		std::istreambuf_iterator<char> begin(fin), end;
		std::string code2(begin,end);
		sources.clear();
		sources.push_back(std::make_pair(code2.c_str(),code2.length()+1));
		// Program is set based on the context and the source
		program = cl::Program(context, sources);

		try{
		  #ifdef USE_DOUBLE_PRECISION
		  program.build(device_vec,"-DUSE_DOUBLE_PRECISION");
		  #else
		  program.build(device_vec);
		  #endif
		}
		catch(cl::Error err)
		{
			std::cerr << "Error failed to build program in " << err.what() << "(" << err.err() << ")" << std::endl;
			std::cerr << program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(deviceSel);
			exit(EXIT_FAILURE);		
		}

		//std::cout << program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(deviceSel)<< std::endl;
	//	std::cout << program.getBuildInfo<CL_PROGRAM_BUILD_STATUS>(deviceSel)<< std::endl;
		//std::cout << program.getBuildInfo<CL_PROGRAM_BUILD_OPTIONS>(deviceSel)<< std::endl;
	}

	friend std::ostream& operator<<(std::ostream& os, const DeviceWrapper& cl);

};

std::ostream& operator<<(std::ostream& os, const DeviceWrapper& cl)
{
	for(std::size_t i = 0; i < cl.devices.size(); ++i)
	{
		os << "Device #" << i << ":" <<std::endl;
		os << "CL_DEVICE_EXTENSIONS\t= " << cl.devices[i].getInfo<CL_DEVICE_EXTENSIONS>() << std::endl;
		os << "CL_DEVICE_GLOBAL_MEM_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_GLOBAL_MEM_SIZE >() << std::endl;
		os << "CL_DEVICE_LOCAL_MEM_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_LOCAL_MEM_SIZE >() << std::endl;
		os << "CL_DEVICE_MAX_CLOCK_FREQUENCY \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_CLOCK_FREQUENCY >() << std::endl;
		os << "CL_DEVICE_MAX_COMPUTE_UNITS \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_COMPUTE_UNITS >() << std::endl;
		os << "CL_DEVICE_MAX_CONSTANT_ARGS \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_CONSTANT_ARGS >() << std::endl;
		os << "CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE >() << std::endl;
		os << "CL_DEVICE_MAX_MEM_ALLOC_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_MEM_ALLOC_SIZE >() << std::endl;
		os << "CL_DEVICE_MAX_PARAMETER_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_PARAMETER_SIZE >() << std::endl;
		os << "CL_DEVICE_MAX_WORK_GROUP_SIZE \t= " << cl.devices[i].getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE >() << std::endl;
		unsigned n =  cl.devices[i].getInfo<CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS >();
		os << "CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS \t= " << n << std::endl;
		os << "CL_DEVICE_MAX_WORK_ITEM_SIZES \t= [" ; 
		for (unsigned k =0; k<n-1; ++k)
		{
			os << cl.devices[i].getInfo<CL_DEVICE_MAX_WORK_ITEM_SIZES >()[k]  << ", ";
		}
		os << cl.devices[i].getInfo<CL_DEVICE_MAX_WORK_ITEM_SIZES >()[n-1]  << "]" << std::endl;
		os << "CL_DEVICE_VERSION  \t= " << cl.devices[i].getInfo< CL_DEVICE_VERSION  >() << std::endl;
		os << "CL_DRIVER_VERSION \t= " << cl.devices[i].getInfo<CL_DRIVER_VERSION>() << std::endl;
	}
	return os;
}
#endif //DEVICE_WRAPPER_HPP
